package com.sp.business.normalization.impl;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sp.business.normalization.impl.KeyGeneratorBase;
import com.sp.business.normalization.impl.NatRuleKeyGenerator;
import com.sp.generated.xml.firemon.InboundAppliedContext;
import com.sp.generated.xml.firemon.Member;
import com.sp.generated.xml.firemon.NATRule;
import com.sp.generated.xml.firemon.NATRuleCollection;
import com.sp.generated.xml.firemon.OutboundAppliedContext;

public class BaseNatRuleKeyGenerator extends NatRuleKeyGenerator
{
  private static final Logger logger = LoggerFactory.getLogger(BaseNatRuleKeyGenerator.class);
  
  final private static int lowestAllowableEditScore = 4;

  @Override
  public void generateNATRuleKeys(NATRuleCollection previous, NATRuleCollection current)
  {
    if (current == null)
      current = new NATRuleCollection();

    // key statistics
    int matchedKeyCount = 0;
    int generatedKeyCount = 0;

    long start = System.currentTimeMillis();

    List<NATRule> nonMatchedNATRules = new ArrayList<NATRule>(current.getNATRules());
    List<NATRule> nonMatchedPreviousNATRules;

    if (previous == null)
      nonMatchedPreviousNATRules = new ArrayList<NATRule>();
    else
      nonMatchedPreviousNATRules = new ArrayList<NATRule>(previous.getNATRules());

    // find exact matches first
    for (NATRule natRule : current.getNATRules())
    {
      NATRule previousNATRule = findNATRuleExactMatch(nonMatchedPreviousNATRules, natRule);

      if (previousNATRule == null)
        continue;

      copyUID(previousNATRule, natRule);

      // remove the previous securityRule, so it doesn't get matched again
      nonMatchedPreviousNATRules.remove(previousNATRule);

      // remove the securityRule, so we don't try to match it again
      nonMatchedNATRules.remove(natRule);

      matchedKeyCount++;
    }

    // now try to match remaining, non-exact-matched securityRules
    for (NATRule natRule : nonMatchedNATRules)
    {
      NATRule previousNATRule = findNATRuleCloseMatch(nonMatchedPreviousNATRules, natRule);

      // copy or generate the guid
      copyUID(previousNATRule, natRule);

      if (previousNATRule == null)
      {
        /* no close match could be found for the securityRule 
         * the securityRule is either a newly-created securityRule, 
         * or it has changed so significantly that 
         * it is effectively a new securityRule.
        */
        generatedKeyCount++;
      }
      else
      {
        // we found a close match,
        // remove the previous securityRule, so it doesn't get matched again
        nonMatchedPreviousNATRules.remove(previousNATRule);

        matchedKeyCount++;
      }
    }

    logger.info(String.format("Matched %s and generated %s Cisco nat rule keys in %s ms.", matchedKeyCount,
        generatedKeyCount, System.currentTimeMillis() - start));
  }

  private static NATRule findNATRuleExactMatch(
      List<NATRule> previousNATRules,
      NATRule natRule)
  {
    for (NATRule previousNATRule : previousNATRules)
    {
      if (isNATRuleExactMatch(previousNATRule, natRule))
        return previousNATRule;
    }

    return null;
  }

  public static boolean isNATRuleExactMatch(NATRule previousNATRule, NATRule natRule)
  { 
	//previous no nat rule
	if(previousNATRule == null)
		return false;
	
	//from	
	if(!isSameFrom(previousNATRule.getFrom(), natRule.getFrom()))
		return false;
	
	//to    
    if(!isSameTo(previousNATRule.getTo(), natRule.getTo()))
		return false;
	  
    //if src, dst and service are equal
	if( previousNATRule.getSource() == null || natRule.getSource()==null)	
		return (previousNATRule.getSource() == natRule.getSource() ) ? true : false;	
	
    List<Member> prevSrcMembers = previousNATRule.getSource().getMembers().getMembers();
    List<Member> srcMembers = natRule.getSource().getMembers().getMembers();
    if (!KeyGeneratorBase.isSameNetworkColumn(prevSrcMembers, srcMembers))
    {
      return false;
    }

    if( previousNATRule.getDestination() == null || natRule.getDestination()==null)
       return (previousNATRule.getDestination() == natRule.getDestination() ) ? true : false;    	
    
    List<Member> prevDstMembers = previousNATRule.getDestination().getMembers().getMembers();
    List<Member> dstMembers = natRule.getDestination().getMembers().getMembers();
    if (!KeyGeneratorBase.isSameNetworkColumn(prevDstMembers, dstMembers))
    {
      return false;
    }

    if( previousNATRule.getService() == null || natRule.getService()==null)
      return ( previousNATRule.getService() == natRule.getService() ) ? true : false;   	 
    
    List<Member> prevSvcMembers = previousNATRule.getService().getMembers().getMembers();
    List<Member> svcMembers = natRule.getService().getMembers().getMembers();
    if (!KeyGeneratorBase.isSameServiceColumn(prevSvcMembers, svcMembers))
    {
      return false;
    }
    
    //source translated
    if( previousNATRule.getSourceTranslated() == null || natRule.getSourceTranslated()==null)    
        return ( previousNATRule.getSourceTranslated() == natRule.getSourceTranslated() ) ? true : false;   
    
    if (!KeyGeneratorBase.isSameNetworkTranslatedColumn(previousNATRule.getSourceTranslated(), natRule.getSourceTranslated()))
    {
      return false;
    }   
    
    
    //dst translated
    if( previousNATRule.getDestinationTranslated() == null || natRule.getDestinationTranslated()==null)
        return ( previousNATRule.getDestinationTranslated()== natRule.getDestinationTranslated() ) ? true : false;        	
    
    if (!KeyGeneratorBase.isSameNetworkTranslatedColumn(previousNATRule.getDestinationTranslated(), natRule.getDestinationTranslated()))
    {
      return false;
    }
    
    
    //service translated
    if( previousNATRule.getServiceTranslated() == null || natRule.getServiceTranslated()==null)
        return ( previousNATRule.getServiceTranslated() == natRule.getServiceTranslated() ) ? true : false;        	
    
    if (!KeyGeneratorBase.isSameServiceTranslatedColumn(previousNATRule.getServiceTranslated(), natRule.getServiceTranslated()))
    {
      return false;
    }

    return true;
  }

  //compare src dst and service
  //identical = 4 points
  //modified (if a member is a object group and there are overlap in object groups )= 3 points
  //changed = 0 points
  //the bigger number the closer
  private static NATRule findNATRuleCloseMatch(
      List<NATRule> previousNATRules,
      NATRule natRule)
  {
    if (previousNATRules == null)
      return null;

    int score = 0;
    NATRule bestMatch = null;

    for (NATRule previousNATRule : previousNATRules)
    {
      int editScore = getEditScore(previousNATRule, natRule);

      if (editScore > score)
      {
        score = editScore;
        bestMatch = previousNATRule;
      }
    }

    // TODO: jcoon - need to make this securityRule matching threshold configurable
    //final float maximumAllowableEditDistance = 50;

    if (score < lowestAllowableEditScore)
      return null;

    return bestMatch;
  }

  /**
   * Find the edit distance between two NATRules.
   * 
   * This is the number of changes needed to change one NATRule into another, 
   * where each change is a single modification (single character difference between names, etc).
   * The bigger score the closer
   * @param previousNATRule
   * @param securityRule
   * @return
   */
  private static int getEditScore(NATRule previousNATRule, NATRule natRule)
  {
    int editScore = 0;
    
    if(previousNATRule==null)
    	return 0;
    	
    //from
    if( previousNATRule.getFrom() == null || natRule.getFrom() == null )
    {
    	if(previousNATRule.getFrom() == null && natRule.getFrom() == null )
    	{
    		editScore += 4;
    	}
    	else
    	{
    		editScore += 0;
    	}
    }
    if(isSameFrom(previousNATRule.getFrom(), natRule.getFrom()))
		editScore +=4;
    
    //to
    if( previousNATRule.getTo() == null || natRule.getTo() == null )
    {
    	if(previousNATRule.getTo() == null && natRule.getTo() == null )
    	{
    		editScore += 4;
    	}
    	else
    	{
    		editScore += 0;
    	}
    }
    
    if(isSameTo(previousNATRule.getTo(), natRule.getTo()))
		editScore +=4;
    
    if( previousNATRule.getSource() == null || natRule.getSource() == null )
    {
    	if(previousNATRule.getSource() == null && natRule.getSource() == null )
    	{
    		editScore += 4;
    	}
    	else
    	{
    		editScore += 0;
    	}
    }    
    
    if(natRule.getSource() != null && previousNATRule.getSource() != null)
    {
	    List<Member> prevSrcMembers = previousNATRule.getSource().getMembers().getMembers();
	    List<Member> srcMembers = natRule.getSource().getMembers().getMembers();
	    if (!KeyGeneratorBase.isSameNetworkColumn(prevSrcMembers, srcMembers))
	    {
	      //do nothing
	    }
	    else
	    {
	      editScore += 4;
	    }
    }
    if( previousNATRule.getDestination() == null || natRule.getDestination() == null )
    {
    	if(previousNATRule.getDestination() == null && natRule.getDestination() == null)
    		editScore += 4;
    	else
    		editScore += 0;    	
    }  
    
    if( previousNATRule.getDestination() !=null && (natRule.getDestination() !=null))
    {
        List<Member> prevDstMembers = previousNATRule.getDestination().getMembers().getMembers();  
    
	    List<Member> dstMembers = natRule.getDestination().getMembers().getMembers();
	    if (!KeyGeneratorBase.isSameNetworkColumn(prevDstMembers, dstMembers))
	    {
	      //do nothing
	    }
	    else
	    {
	      editScore += 4;
	    }
    }

    if( previousNATRule.getService() == null || natRule.getService() == null )
    {
    	if(previousNATRule.getService() == null && natRule.getService() == null)
    		editScore += 4;
    	else
    		editScore += 0;
    }    
    
    if( previousNATRule.getService() !=null && (natRule.getService() !=null))    	
    {
	    List<Member> prevSvcMembers = previousNATRule.getService().getMembers().getMembers();
	    List<Member> svcMembers = natRule.getService().getMembers().getMembers();
	    if (!KeyGeneratorBase.isSameServiceColumn(prevSvcMembers, svcMembers))
	    {
	      //do nothing?
	    }
	    else
	    {
	      editScore += 4;
	    }
    }
    
    //src translated
    if( previousNATRule.getSourceTranslated() == null || natRule.getSourceTranslated()==null)
    {
         if( previousNATRule.getSourceTranslated() == null && natRule.getSourceTranslated()==null) 
        	 editScore += 4;
        else
             editScore +=0;
    }
    
    if (KeyGeneratorBase.isSameNetworkTranslatedColumn(previousNATRule.getSourceTranslated(), natRule.getSourceTranslated()))
    {
      editScore += 4;
    }
    
    if( previousNATRule.getDestinationTranslated() == null || natRule.getDestinationTranslated()==null)
    {
         if( previousNATRule.getDestinationTranslated() == null && natRule.getDestinationTranslated()==null) 
        	 editScore +=4;  
         else 
        	 editScore +=0;
    }
    
    if (KeyGeneratorBase.isSameNetworkTranslatedColumn(previousNATRule.getDestinationTranslated(), natRule.getDestinationTranslated()))
    {
        editScore += 4;     
    }    
    
    //service translated
    if( previousNATRule.getServiceTranslated() == null || natRule.getServiceTranslated()==null)
    {
        if( previousNATRule.getServiceTranslated() == null && natRule.getServiceTranslated()==null) 
    	
    	   editScore += 4;
        else
           editScore += 0;
    }
    
    if (KeyGeneratorBase.isSameServiceTranslatedColumn(previousNATRule.getServiceTranslated(), natRule.getServiceTranslated()))
    {
    	editScore += 4;  
    }

    return editScore;
  } 
  
  public static boolean isSameFrom(final InboundAppliedContext previousFrom, final InboundAppliedContext newFrom)
  {
    if(previousFrom == null || newFrom == null)
    	return ( previousFrom == newFrom ) ? true : false; 
    
    if(previousFrom.getIsAny() == null || newFrom.getIsAny()==null)
    	return (previousFrom.getIsAny() == newFrom.getIsAny()) ? true : false;
    
    if(previousFrom.getIsAny().isValue() != newFrom.getIsAny().isValue()) return false;   
    
    //interfaces
    if(previousFrom.getInterfaces() == null || newFrom.getInterfaces() ==null)
    	return (previousFrom.getInterfaces() == newFrom.getInterfaces() ) ? true : false;    		
  
    if(!KeyGeneratorBase.isSameInterfaceReference(previousFrom.getInterfaces(), newFrom.getInterfaces()))
    	return false;
    
    //zones
   
    if(!KeyGeneratorBase.isSameZoneReference(previousFrom.getZones(), newFrom.getZones()))
    	return false;
    
    //routing instance
    
    if(!KeyGeneratorBase.isSameRoutingInstanceReference(previousFrom.getRoutingInstances(), newFrom.getRoutingInstances()))
    	return false;
    	
    return true;
  }
  
  public static boolean isSameTo(final OutboundAppliedContext previousTo, final OutboundAppliedContext newTo) 
  {
	  if(previousTo == null || newTo == null)
	    	return (previousTo == newTo ) ? true : false;		  
	    
	  //if both are any then no need to look further
	    if(previousTo.getIsAny() ==null || newTo.getIsAny()==null) 
	      return (previousTo.getIsAny()== newTo.getIsAny()) ? true : false;
	    
	    if(previousTo.getIsAny().isValue() != newTo.getIsAny().isValue()) return false; 
	    
	    //interfaces	 
	    if(!KeyGeneratorBase.isSameInterfaceReference(previousTo.getInterfaces(), newTo.getInterfaces()))
	    	return false;
	    
	    //zones	   
	    if(!KeyGeneratorBase.isSameZoneReference(previousTo.getZones(), newTo.getZones()))
	    	return false;
	    
	    //routing instance	   
	    if(!KeyGeneratorBase.isSameRoutingInstanceReference(previousTo.getRoutingInstances(), newTo.getRoutingInstances()))
	    	return false;
	    
    return true;
  }  

}